package com.aabte.commons.jackson;

import com.aabte.commons.json.JSONException;
import com.aabte.commons.json.JSONIOException;
import com.aabte.commons.json.JSONSerializationAdapter;
import com.aabte.commons.json.JSONSyntaxException;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Type;
import java.util.List;

/**
 * @author Daniel
 * @version 1.0
 * @date 2020/4/5
 */
public class JacksonSerializationAdapter implements JSONSerializationAdapter {

  private static ObjectMapper objectMapper = new ObjectMapper();

  static {
    objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    objectMapper.setFilterProvider(new SimpleFilterProvider().setFailOnUnknownId(false));
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
  }

  public static ObjectMapper getObjectMapper() {
    return objectMapper;
  }

  public static void setObjectMapper(ObjectMapper objectMapper) {
    JacksonSerializationAdapter.objectMapper = objectMapper;
  }

  @Override
  public String toJson(Object src) throws JSONException {
    try {
      return objectMapper.writeValueAsString(src);
    } catch (JsonProcessingException e) {
      throw new JSONException(e);
    }
  }

  @Override
  public String toJson(Object src, Type typeOfSrc) {
    return toJson(src);
  }

  @Override
  public void toJson(Object src, Appendable writer) throws JSONIOException {
    try {
      writer.append(toJson(src));
    } catch (IOException e) {
      throw new JSONIOException(e);
    }
  }

  @Override
  public <T> T fromJson(String json, Type typeOfT) throws JSONSyntaxException {
    JavaType javaType = objectMapper.getTypeFactory().constructType(typeOfT);
    try {
      return objectMapper.readerFor(javaType).readValue(json);
    } catch (JsonProcessingException e) {
      throw new JSONSyntaxException(e);
    }
  }

  @Override
  public <T> T fromJson(String json, Class<T> classOfT) {
    TypeReference<T> typeReference = new TypeReference<T>() {
    };
    return fromJson(json, typeReference);
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T> T fromJson(String json, Class<T> classOfT, Type... typeArguments)
      throws JSONSyntaxException {

    if (null == typeArguments || typeArguments.length == 0) {
      TypeReference<T> typeReference = new TypeReference<T>() {
      };
      return fromJson(json, typeReference);
    }

    JavaType javaType = getJavaType(classOfT, (Class<?>[]) typeArguments);
    ObjectReader objectReader = objectMapper.readerFor(javaType);
    try {
      return (T) objectReader.readValue(json);
    } catch (JsonProcessingException e) {
      throw new JSONSyntaxException(e);
    }
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T> T fromJsonArray(String json, Type typeArguments) throws JSONSyntaxException {
    return (T) fromJson(json, List.class, typeArguments);
  }

  public static JavaType getJavaType(Class<?> collectionClass, Class<?>... elementClasses) {
    return objectMapper.getTypeFactory()
        .constructParametricType(collectionClass, elementClasses);
  }

  @SuppressWarnings("unchecked")
  public <T> T fromJson(String json, TypeReference<T> typeReference) {
    try {
      return (T)
          (typeReference.getType().equals(String.class)
              ? json
              : objectMapper.readValue(json, typeReference));
    } catch (JsonProcessingException e) {
      throw new JSONSyntaxException(e);
    }
  }

  @Override
  public <T> T fromJson(Reader json, Class<T> classOfT) {
    TypeReference<T> typeReference = new TypeReference<T>() {};
    return fromJson(json, typeReference);
  }

  @Override
  public <T> T fromJson(Reader json, Type typeOfT) {
    JavaType javaType = objectMapper.getTypeFactory().constructType(typeOfT);
    try {
      return objectMapper.readerFor(javaType).readValue(json);
    } catch (IOException e) {
      throw new JSONSyntaxException(e);
    }
  }

  @SuppressWarnings("unchecked")
  public <T> T fromJson(Reader json, TypeReference<T> typeReference) {
    try {
      return (T)
          (typeReference.getType().equals(String.class)
              ? json
              : objectMapper.readValue(json, typeReference));
    } catch (JsonProcessingException e) {
      throw new JSONSyntaxException(e);
    } catch (IOException e) {
      throw new JSONIOException(e);
    }
  }
}
